package com.qb.plugin;

import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.Format;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.Transform;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.qb.plugin.asm.BaseWeaver;
import com.qb.plugin.concurrent.Schedulers;
import com.qb.plugin.concurrent.Worker;

import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * File: null.java
 * Creator: dingpwen
 * Company: shiwei
 * Created on: 2021/7/2 15:27
 * Email: dingpwen@gmail.com
 * Details: TODO
 */
public class BaseTransform extends Transform {
    private final Project project;
    private final Logger logger;
    private final Worker worker;
    protected BaseWeaver bytecodeWeaver;

    public BaseTransform(Project project){
        this.project = project;
        this.logger = project.getLogger();
        this.worker = Schedulers.IO();
    }

    @Override
    public String getName() {
        return "CommonTransform";
    }

    /*@Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        //当前是否是增量编译
        boolean increment = transformInvocation.isIncremental();
        //消费型输入，可以从中获取jar包和class文件夹路径。需要输出给下一个任务
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //引用型输入，无需输出。
        Collection<TransformInput> referInputs = transformInvocation.getReferencedInputs();
        TransformOutputProvider provider = transformInvocation.getOutputProvider();
        for(TransformInput input:inputs){
            for(JarInput jarInput:input.getJarInputs()){
                File dest = provider.getContentLocation(
                        jarInput.getFile().getAbsolutePath(),
                        jarInput.getContentTypes(),
                        jarInput.getScopes(),
                        Format.JAR
                );
                //将修改过的字节码copy到dest，就可以实现编译期间干预字节码的目的了
                FileUtils.copyFile(jarInput.getFile(), dest);
            }
            for(DirectoryInput directoryInput:input.getDirectoryInputs()){
                File dest = provider.getContentLocation(
                        directoryInput.getName(),
                        directoryInput.getContentTypes(),
                        directoryInput.getScopes(),
                        Format.DIRECTORY
                );
                //将修改过的字节码copy到dest，就可以实现编译期间干预字节码的目的了
                FileUtils.copyFile(directoryInput.getFile(), dest);
            }
        }
    }*/

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        boolean increment = transformInvocation.isIncremental();
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //引用型输入，无需输出。
        Collection<TransformInput> referInputs = transformInvocation.getReferencedInputs();
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
        long startTime = System.currentTimeMillis();

        if(!increment){
            outputProvider.deleteAll();
        }

        for(TransformInput input:inputs){
            for(JarInput jarInput:input.getJarInputs()){
                Status status = jarInput.getStatus();
                File dest = outputProvider.getContentLocation(jarInput.getName(),
                        jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);
                if(increment){
                    switch(status){
                        case NOTCHANGED:
                            break;
                        case ADDED:
                        case CHANGED:
                            transformJar(jarInput.getFile(), dest, status);
                            break;
                        case REMOVED:
                            if(dest.exists()){
                                FileUtils.forceDelete(dest);
                            }
                            break;
                    }
                } else {
                    transformJar(jarInput.getFile(), dest, status);
                }
            }
            for(DirectoryInput directoryInput:input.getDirectoryInputs()){
                File dest = outputProvider.getContentLocation(directoryInput.getName(),
                        directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);
                FileUtils.forceMkdir(dest);
                if(increment){
                    String srcDirPath = directoryInput.getFile().getAbsolutePath();
                    String destDirPath = dest.getAbsolutePath();
                    Map<File, Status> changeFiles =  directoryInput.getChangedFiles();
                    for(Map.Entry<File, Status> changeFile:changeFiles.entrySet()){
                        Status status = changeFile.getValue();
                        File inputFile = changeFile.getKey();
                        String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath);
                        File destFile = new File(destFilePath);
                        switch (status){
                            case NOTCHANGED:
                                break;
                            case ADDED:
                            case CHANGED:
                                FileUtils.touch(destFile);
                                transformSingleFile(inputFile, destFile, srcDirPath);
                                break;
                            case REMOVED:
                                if(destFile.exists()){
                                    FileUtils.forceDelete(destFile);
                                }
                                break;
                        }
                    }
                } else {
                    transformDir(directoryInput.getFile(), dest);
                }
            }
        }

        worker.await();
        long costTime = System.currentTimeMillis() - startTime;
        logger.warn((getName() + " costed " + costTime + "ms"));
    }

    private void transformJar(final File srcJar, final File destJar, Status status) {
        worker.submit(() -> {
            bytecodeWeaver.weaveJar(srcJar, destJar);
            return null;
        });
    }

    private void transformDir(final File inputDir, final File outputDir) throws IOException {
        final String inputDirPath = inputDir.getAbsolutePath();
        final String outputDirPath = outputDir.getAbsolutePath();
        if (inputDir.isDirectory()) {
            for (final File file : com.android.utils.FileUtils.getAllFiles(inputDir)) {
                worker.submit(() -> {
                    String filePath = file.getAbsolutePath();
                    File outputFile = new File(filePath.replace(inputDirPath, outputDirPath));
                    bytecodeWeaver.weaveSingleClassToFile(file, outputFile, inputDirPath);
                    return null;
                });
            }
        }
    }

    //private WaitableExecutor waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool();

    private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) {
        //异步并发处理jar/class
        worker.submit(() -> {
            bytecodeWeaver.weaveSingleClassToFile(inputFile, outputFile, srcBaseDir);
            return null;
        });
    }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    @Override
    public boolean isIncremental() {
        return true;
    }

    @Override
    public boolean isCacheable() {
        return true;
    }
}
